﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Xml.Serialization;
using System.Linq;
using System.IO;
using System.Globalization;

using Fuse8.DomainFramework.Domain;
using Fuse8.DomainFramework.Configuration;

namespace Fuse8.DomainFramework.DataAccess.XmlDatabase
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    /// <typeparam name="TKey"></typeparam>
    public abstract class XmlFileDB<TEntity, TKey> 
        where TEntity : class, IEntityObject<TKey>
        where TKey : IComparable<TKey>, IEquatable<TKey> 
    {
        #region Fields

        private static object _syncRoot = new object();

        private static XmlSerializer _serializer = new XmlSerializer(typeof(XmlFileDBEntry<TEntity>));

        private static string _fileName = string.Empty;

        private List<TEntity> _repository = new List<TEntity>();

        private DateTime _lastAccess = DateTime.MinValue;

        #endregion

        #region Property

        /// <summary>
        /// 
        /// </summary>
        public IQueryable<TEntity> Repository
        {
            get
            {
                lock (_syncRoot)
                {
                    Init();
                }

                return new ReadOnlyCollection<TEntity>(_repository).AsQueryable();
            }
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Add the specified entity object to the database
        /// </summary>
        public void Add(TEntity entityObject)
        {
            lock (_syncRoot)
            {
                Init();

                entityObject.Id = _repository.Count > 0 ?
                    _repository.Max(p => p.Id) :
                    default(TKey);

                entityObject.IncreaseId();

                _repository.Add(entityObject);

                using (FileStream stream = GetStream(false))
                {
                    using (StreamWriter sw = new StreamWriter(stream))
                    {
                        _serializer.Serialize(sw, new XmlFileDBEntry<TEntity> { Entries = _repository });
                    }
                }
            }
        }

        /// <summary>
        /// Remove the specified entity object from the database
        /// </summary>
        public void Remove(TEntity entityObject)
        {
            lock (_syncRoot)
            {
                Init();
                
                _repository.Remove(entityObject);

                using (FileStream stream = GetStream(false))
                {
                    using (StreamWriter sw = new StreamWriter(stream))
                    {
                        _serializer.Serialize(sw, new XmlFileDBEntry<TEntity> { Entries = _repository });
                    }
                }
            }
        }

        /// <summary>
        /// Save the specified entity object in the database
        /// </summary>
        public void Save(TEntity entityObject)
        {
            lock (_syncRoot)
            {
                Init();
                
                if (_repository.Contains(entityObject))
                {
                    int itemIndex = _repository.IndexOf(entityObject);
                    if (itemIndex >= 0)
                    {
                        _repository[itemIndex] = entityObject;

                        using (FileStream stream = GetStream(false))
                        {
                            using (StreamWriter sw = new StreamWriter(stream))
                            {
                                _serializer.Serialize(sw, new XmlFileDBEntry<TEntity> { Entries = _repository });
                            }
                        }
                    }
                }
            }
        } 

        #endregion

        #region Private and protected methods

        private void Init()
        {
            if (string.IsNullOrEmpty(_fileName))
            {
                _fileName = Path.Combine(
                    XmlDatabaseConfiguration.XmlDBPath,
                    string.Format(CultureInfo.InvariantCulture, "{0}.xml", CreateDomainEntity().TypeParameter));
            }
            
            LoadData();
        } 
        
        /// <summary>
        /// 
        /// </summary>
        protected virtual void LoadData()
        {            
            using (FileStream stream = GetStream(true))
            {
                DateTime lastAccess = File.GetLastWriteTime(_fileName);

                if (!_lastAccess.Equals(lastAccess))
                {
                    using (StreamReader sr = new StreamReader(stream))
                    {
                        try
                        {
                            XmlFileDBEntry<TEntity> entry = _serializer.Deserialize(sr) as XmlFileDBEntry<TEntity>;
                            if (entry != null)
                            {
                                _repository = entry.Entries;

                                _lastAccess = lastAccess;
                            }
                        }
                        catch (InvalidOperationException)
                        { }
                    }
                }
            }
        }

        private static FileStream GetStream(bool isForRead)
        {
            FileStream result = null;            
            
            if (File.Exists(_fileName))
            {
                if (isForRead)
                {
                    result = File.Open(_fileName, FileMode.Open, FileAccess.Read, FileShare.None);
                }
                else
                {
                    result = File.Create(_fileName);
                }
            }
            else
            {
                if (!Directory.Exists(XmlDatabaseConfiguration.XmlDBPath))
                {
                    Directory.CreateDirectory(XmlDatabaseConfiguration.XmlDBPath);
                }

                result = File.Create(_fileName);
            }

            return result;
        } 

        #endregion

        #region Abstraction

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public abstract TEntity CreateDomainEntity();

        #endregion
    }
}
